﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace SStuff.XnaInterop
{
    public partial class InteropModel
    {
        public static InteropModel FromXnaModel(Model model)
        {
            InteropModel result = new InteropModel();

            List<VertexBuffer> allVertexBuffers = new List<VertexBuffer>();
            List<IndexBuffer> allIndexBuffers = new List<IndexBuffer>();
            List<Texture2D> allTextures = new List<Texture2D>();
            List<InteropMesh> meshes = new List<InteropMesh>();
            List<InteropBone> bones = new List<InteropBone>();

            foreach (var b in model.Bones)
            {
                bones.Add(new InteropBone
                {
                    IsRoot = b.Parent == null,
                    Name = b.Name,
                    ParentIndex = b.Parent == null ? 0 : b.Parent.Index,
                    Transform = b.Transform,
                    Index=b.Index
                });
            }
            result.Bones = bones.ToArray();

            // extracting vertex buffers, index buffers and textures
            foreach (var part in model.Meshes.SelectMany(m => m.MeshParts))
            {
                if (!allVertexBuffers.Contains(part.VertexBuffer))
                    allVertexBuffers.Add(part.VertexBuffer);
                if (!allIndexBuffers.Contains(part.IndexBuffer))
                    allIndexBuffers.Add(part.IndexBuffer);

                foreach (var texParam in part.Effect.Parameters.Where(p => p.ParameterType == EffectParameterType.Texture2D))
                {
                    var tex2D = texParam.GetValueTexture2D();
                    if (tex2D != null && !allTextures.Contains(tex2D))
                        allTextures.Add(tex2D);
                }
            }

            // extracting info about each mesh (bounding sphere, effect names / parameters, texture usages)
            foreach (var sourceMesh in model.Meshes)
            {
                var mesh = new InteropMesh
                {
                    Name = sourceMesh.Name,
                    BoundingSphereCenterX = sourceMesh.BoundingSphere.Center.X,
                    BoundingSphereCenterY = sourceMesh.BoundingSphere.Center.Y,
                    BoundingSphereCenterZ = sourceMesh.BoundingSphere.Center.Z,
                    BoundingSphereRadius = sourceMesh.BoundingSphere.Radius,
                    BoneIndex = sourceMesh.ParentBone == null ? 0 : sourceMesh.ParentBone.Index
                };
                meshes.Add(mesh);
                List<InteropMeshPart> meshParts = new List<InteropMeshPart>();
                foreach (var sourcePart in sourceMesh.MeshParts)
                {
                    var part = new InteropMeshPart();
                    meshParts.Add(part);
                    part.EffectName = sourcePart.Effect.Name;
                    part.EffectTechnique = sourcePart.Effect.CurrentTechnique.Name;
                    part.IndexBufferIndex = allIndexBuffers.IndexOf(sourcePart.IndexBuffer);
                    part.NumVertices = sourcePart.NumVertices;
                    part.PrimitiveCount = sourcePart.PrimitiveCount;
                    part.StartIndex = sourcePart.StartIndex;
                    part.VertexBufferIndex = allVertexBuffers.IndexOf(sourcePart.VertexBuffer);
                    part.VertexOffset = sourcePart.VertexOffset;
                    part.TextureUsages =
                        sourcePart.Effect.Parameters.Where(p => p.ParameterType == EffectParameterType.Texture2D)
                            .Select(
                                ep =>
                                new InteropTextureUsage
                                {
                                    EffectParameterName = ep.Name,
                                    TextureIndex = allTextures.IndexOf(ep.GetValueTexture2D())
                                })
                            .ToArray();
                    part.EffectParameters =
                        sourcePart.Effect.Parameters.Where(p => p.ParameterType != EffectParameterType.Texture2D && p.ParameterType != EffectParameterType.Texture1D &&
                            p.ParameterType != EffectParameterType.Texture3D && p.ParameterType != EffectParameterType.TextureCube && p.ParameterType != EffectParameterType.Void)
                            .Select(ep => new InteropEffectParameter
                            {
                                ParameterName = ep.Name,
                                Data = ExtractParameterValue(ep)
                            }).ToArray();
                }
                mesh.Parts = meshParts.ToArray();
            }

            // creating data from Vertex / Index buffers and textures in interoperable ways
            result.IndexBuffers = allIndexBuffers.Select(ib => InteropIndexBuffer.FromXnaIndexBuffer(ib)).ToArray();
            result.VertexBuffers = allVertexBuffers.Select(vb => InteropVertexBuffer.FromXnaVertexBuffer(vb)).ToArray();
            result.Textures =
                allTextures.Select(
                    tex => new InteropTexture2D { Height = tex.Height, Width = tex.Width, PngData = GetTexturePng(tex) }).ToArray();
            result.Meshes = meshes.ToArray();
            return result;
        }

        private static Vector4[] ExtractParameterValue(EffectParameter ep)
        {
            switch (ep.ParameterClass)
            {
                case EffectParameterClass.Vector:
                    switch (ep.ColumnCount)
                    {
                        case 2:
                            return new Vector4[] { new Vector4(ep.GetValueVector2(), 0, 0) };
                        case 3:
                            return new Vector4[] { new Vector4(ep.GetValueVector3(), 0) };
                        case 4:
                            return new Vector4[] { ep.GetValueVector4() };
                    }
                    break;
                case EffectParameterClass.Scalar:
                    return new Vector4[] { new Vector4(ep.GetValueSingle(),0,0,0) };
            }

            return null;
        }
        static byte[] GetTexturePng(Texture2D source)
        {
            var ms = new MemoryStream();
            source.SaveAsPng(ms, source.Width, source.Height);
            return ms.ToArray();
        }
    }



    public partial class InteropVertexBuffer
    {
        public static InteropVertexBuffer FromXnaVertexBuffer(VertexBuffer vb)
        {
            var result = new InteropVertexBuffer();
            result.VertexCount = vb.VertexCount;
            result.VertexStride = vb.VertexDeclaration.VertexStride;
            result.Elements = vb.VertexDeclaration.GetVertexElements()
                .Select(
                    ve =>
                    new InteropVertexElement()
                    {
                        Format = ve.VertexElementFormat.ToString(),
                        Offset = ve.Offset,
                        Usage = ve.VertexElementUsage.ToString(),
                        UsageIndex = ve.UsageIndex
                    })
                .ToArray();
            result.Data = new byte[result.VertexCount * result.VertexStride];
            vb.GetData(result.Data);
            return result;
        }
    }

    public partial class InteropIndexBuffer
    {
        public static InteropIndexBuffer FromXnaIndexBuffer(IndexBuffer buffer)
        {
            var result = new InteropIndexBuffer();
            result.IndexCount = buffer.IndexCount;
            result.Is32BitFormat = false;
            bool thirty2 = buffer.IndexElementSize == IndexElementSize.ThirtyTwoBits;
            if (thirty2)
            {
                ushort[] output = new ushort[result.IndexCount];
                int[] data = new int[result.IndexCount];
                buffer.GetData(data);
                for (int i = 0; i < result.IndexCount; i++)
                {
                    var val = data[i];
                    if (val > ushort.MaxValue)
                        throw new InvalidOperationException("Index buffer reffers to unreachable vertex");
                    output[i] = (ushort)val;
                }
                result.DataInt16 = output;
            }
            else
            {
                ushort[] data = new ushort[result.IndexCount];
                buffer.GetData(data);
                result.DataInt16 = data;
            }
            return result;
        }
    }
}
